#import "L0PlugSuitPreferencePane.h"
#import "L0PlugSuitRegistry.h"
#import "L0PlugSuitNotNullTransformer.h"
#import "LoginItemsAE.h"
#import "L0PlugSuitTableItem.h"

#import <Security/Security.h>

#define L0PlugSuitLocalizedString(x, y) ([[NSBundle bundleForClass:[self class]] localizedStringForKey:(x) value:(x) table:nil])

static AuthorizationRef _authorization = NULL;

static AuthorizationRef L0PlugSuitGetAuthorization() {
    if (_authorization)
        return _authorization;
    
    if (AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &_authorization) == noErr) {
        AuthorizationItem authItems[1];
        authItems[0].name = kAuthorizationRightExecute;
        authItems[0].valueLength = 0;
        authItems[0].value = NULL;
        authItems[0].flags = 0;
        
        AuthorizationRights executionRights;
        executionRights.count = 1;
        executionRights.items = authItems;
        
        if (AuthorizationCopyRights(_authorization, &executionRights, kAuthorizationEmptyEnvironment, kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed, NULL) == noErr)
            return _authorization;
        
        AuthorizationFree(_authorization, kAuthorizationFlagDestroyRights);
    }
    
    return NULL;
}

static void L0PlugSuitClearAuthorization() {
    if (_authorization) {
        AuthorizationFree(_authorization, kAuthorizationFlagDestroyRights);
        _authorization = NULL;
    }
}

@implementation L0PlugSuitPreferencePane

- (BOOL) requiresAuthorization {
	//liberally copied and adapted from Afloat b4.
	NSString* pathToAgentBundle = [[NSBundle bundleForClass:[self class]] pathForResource:@"PlugSuit" ofType:@"app"];
	NSBundle* agentBundle = [[[NSBundle alloc] initWithPath:pathToAgentBundle] autorelease];
	NSDictionary* stats = [[NSFileManager defaultManager] fileAttributesAtPath:[agentBundle executablePath] traverseLink:NO];
	
	if ([[stats objectForKey:NSFileGroupOwnerAccountID] longValue] == 9 &&
		[[stats objectForKey:NSFilePosixPermissions] longValue] == 02755)
		return NO;
	
	long minor, build;
	if (Gestalt(gestaltSystemVersionMinor, &minor) != noErr) {
		// ABORT! Todo.
		return NO;
	}
	
	if (Gestalt(gestaltSystemVersionBugFix, &build) != noErr) {
		// ABORT! Todo.
		return NO;
	}
#if defined(__i386__)
	return (minor == 4 && build >= 4) || (minor >= 5); // i386 10.4.4 and later.
#else
    return (minor >= 5); // PPC Leopard and later
#endif
}

- (void) _authorization:(NSAlert*) auth didEndWithCode:(int) code nothing:(void*) nothing {
	if (code == NSAlertSecondButtonReturn) /* cancel */
		return;
	
	if ([self authorize])
		[self enable];
}

- (BOOL) authorize {
	AuthorizationRef auth;
	
	if (auth = L0PlugSuitGetAuthorization()) {		
		NSString* pathToAgentBundle = [[NSBundle bundleForClass:[self class]] pathForResource:@"PlugSuit" ofType:@"app"];
		NSBundle* agentBundle = [[[NSBundle alloc] initWithPath:pathToAgentBundle] autorelease];
		const char* pathToAgentExecC = [[NSFileManager defaultManager] fileSystemRepresentationWithPath:[agentBundle executablePath]];
		
		char* args[] = {
			"--L0Authorize",
			NULL
		};
		
		AuthorizationExecuteWithPrivileges(auth, pathToAgentExecC, kAuthorizationFlagDefaults, args, NULL);
		L0PlugSuitClearAuthorization();
		
		usleep(500000);
	} else
		return NO;
	
	return ![self requiresAuthorization];
}

- (NSImage*) imageNamed:(NSString*) name {
	NSString* path = [[NSBundle bundleForClass:[self class]] pathForImageResource:name];
	if (path)
		return [[[NSImage alloc] initWithContentsOfFile:path] autorelease];
	else
		return nil;
}

- (id) injectorWire {
	@try {
		id wire = [NSConnection rootProxyForConnectionWithRegisteredName:kL0PlugSuitConnectionName host:nil];
		[wire setProtocolForProxy:@protocol(L0PlugSuitWire)];
		return wire;
	} @catch (id exception) {}
	
	return nil;
}

- (void) _updateState:(id) timer {
	if ([self injectorWire]) {
		[_icon setImage:[self imageNamed:@"PlugSuit128"]];
		[_light setImage:[self imageNamed:@"GreenDot"]];
		
		[_enableField setStringValue:L0PlugSuitLocalizedString(@"PlugSuit is enabled.", @"Enabled/Disabled field")];
		[_enableButton setTitle:L0PlugSuitLocalizedString(@"Disable", @"Enabled/Disabled button")];
	} else {
		[_icon setImage:[self imageNamed:@"PlugSuit128_disabled"]];
		[_light setImage:[self imageNamed:@"RedDot"]];

		[_enableField setStringValue:L0PlugSuitLocalizedString(@"PlugSuit is disabled.", @"Enabled/Disabled field")];
		[_enableButton setTitle:L0PlugSuitLocalizedString(@"Enable", @"Enabled/Disabled button")];
	}
	
	[_enableButton setEnabled:YES];
	[_enableSpinner stopAnimation:self];
}

- (void) _reactivate {
	[NSApp activateIgnoringOtherApps:YES];
	[[[NSApp orderedWindows] objectAtIndex:0] makeKeyAndOrderFront:self];	
}

- (IBAction) toggleEnabling:(id) sender {
	id wire = [self injectorWire];
	if (wire) {
		@try {
			[wire shutDown];
		} @catch (id exception) {}
		
		NSArray* loginItems = nil;
		LIAECopyLoginItems((CFArrayRef*) &loginItems);
		[loginItems autorelease];
		
		NSString* path = [[NSBundle bundleForClass:[self class]] pathForResource:@"PlugSuit" ofType:@"app"];
		NSURL* url = [NSURL fileURLWithPath:path];
		
		int i, j;
		for (i = 0, j = 0; i < [loginItems count]; i++, j++) {
			// no nsenumerators, because we're going to need the index
			if ([[[loginItems objectAtIndex:i] objectForKey:(id) kLIAEURL] isEqual:url]) {
				LIAERemove((CFIndex)j);
				j--;
			}
		}
	} else {
		if ([self requiresAuthorization]) {
			NSAlert* alert = [NSAlert new];
			[alert setMessageText:L0PlugSuitLocalizedString(@"PlugSuit requires your authorization.", @"Needs auth panel message")];
			[alert setInformativeText:L0PlugSuitLocalizedString(@"Mac OS X's security requires an administrator's name and password to authorize PlugSuit. You need to do this only once until the next time PlugSuit is updated.", @"Needs auth panel info text")];
			[alert addButtonWithTitle:L0PlugSuitLocalizedString(@"Authorize", @"Authorize button")];
			[alert addButtonWithTitle:L0PlugSuitLocalizedString(@"Cancel", @"Cancel button")];
			[alert beginSheetModalForWindow:[[self mainView] window] modalDelegate:self didEndSelector:@selector(_authorization:didEndWithCode:nothing:) contextInfo:NULL];
			[alert autorelease];
		} else
			[self enable];
	}

	[_enableSpinner startAnimation:self];
}

- (void) enable {	
	NSString* path = [[NSBundle bundleForClass:[self class]] pathForResource:@"PlugSuit" ofType:@"app"];
	NSURL* url = [NSURL fileURLWithPath:path];
	
	NSWorkspace* ws = [NSWorkspace sharedWorkspace];
	[ws openFile:path];
	[self performSelector:@selector(_reactivate) withObject:nil afterDelay:0.5];
	
	NSArray* items = nil;
	LIAECopyLoginItems((CFArrayRef*) &items);
	if (items && ![items containsObject:url])
		LIAEAddURLAtEnd((CFURLRef) url, (Boolean) YES);
	[items release];
}

- (IBAction) showSelectedPlugIn:(id) sender {
	if ([[_controller selectedObjects] count] == 0) {
		NSBeep();
		return;
	}
	
	id selected = [[_controller selectedObjects] objectAtIndex:0];
	NSString* path = [selected valueForKey:@"path"];
	
	if (!path || ![[NSWorkspace sharedWorkspace] selectFile:path inFileViewerRootedAtPath:[path stringByDeletingLastPathComponent]])
		NSBeep();
}

+ (void) initialize {
	[L0PlugSuitNotNullTransformer registerClass];
}

- (NSString*) mainNibName { return @"PreferencePane"; }

- (void) willSelect {
	_shownRestartWarning = NO;
	
	[self _updateRegisteredPlugIns];
	[self _updateState:self];
	
	if (!_timer)
		_timer = [[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(_updateState:) userInfo:nil repeats:YES] retain];
}

- (void) didUnselect {
	[_enableSpinner stopAnimation:self];
	if (_timer) {
		[_timer invalidate];
		[_timer release];
		_timer = nil;
	}
}

- (void) _updateRegisteredPlugIns {
	[L0PlugSuitRegistry updateRegistries];
	[self setRegisteredPlugInsFromDictionary:[L0PlugSuitRegistry registeredPlugIns]];
}

- (NSArray*) registeredPlugIns {
	return _plugIns;
}

- (void) setRegisteredPlugInsFromDictionary:(NSDictionary*) dictionary {
	[self willChangeValueForKey:@"registeredPlugIns"];
	[_plugIns release];
	_plugIns = [NSMutableArray new];
	
//	NSWorkspace* ws = [NSWorkspace sharedWorkspace];
//	NSFileManager* fm = [NSFileManager defaultManager];
	
	NSEnumerator* enu = [[dictionary allValues] objectEnumerator];
	NSBundle* bundle;
	while (bundle = [enu nextObject]) {
//		NSString* path = [bundle bundlePath];
//		
//		NSString* name = [[[bundle infoDictionary] objectForKey:@"CFBundleName"] description];
//		if (!name)
//			name = [fm displayNameAtPath:path];
//		
//		NSImage* icon = nil;
//		NSString* iconPath;
//		if (iconPath = [[bundle infoDictionary] objectForKey:@"CFBundleIcon"]) {
//			NSString* type = ([iconPath rangeOfString:@"."].location != NSNotFound)? @"" : @"icns";
//			iconPath = [bundle pathForResource:iconPath ofType:type];
//			
//			if ([fm fileExistsAtPath:iconPath])
//				icon = [[[NSImage alloc] initWithContentsOfFile:iconPath] autorelease];
//		}
//		
//		if (!icon)
//			icon = [ws iconForFile:path];
//		[icon setSize:NSMakeSize(32, 32)];
//		
//		id version = [[[bundle infoDictionary] objectForKey:@"CFBundleShortVersionString"] description];
//		if (!version)
//			version = [[[bundle infoDictionary] objectForKey:@"CFBundleVersion"] description];
//		
//		id longName = [NSNull null];
//		if (!version)
//			version = [NSNull null];
//		else
//			longName = [NSString stringWithFormat:@"%@ (%@)", name, version];
//		
//		NSDictionary* plugInData = [NSDictionary dictionaryWithObjectsAndKeys:
//			path, @"path",
//			name, @"name",
//			icon, @"icon",
//			version, @"version",
//			longName, @"longName",
//			nil];
		L0PlugSuitTableItem* plugInData = [[L0PlugSuitTableItem alloc] initWithBundle:bundle pane:self];
		[_plugIns addObject:[plugInData autorelease]];
	}
	
	[self didChangeValueForKey:@"registeredPlugIns"];
}

- (void) dealloc {
	[self setRegisteredPlugInsFromDictionary:nil];
	[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
	[super dealloc];
}

- (void) warnAboutApplicationRestart:(BOOL) willEnable {
	if (_shownRestartWarning)
		return;
	
	_shownRestartWarning = YES;
	
	NSAlert* alert = [NSAlert new];
	[alert setMessageText:L0PlugSuitLocalizedString(@"You might need to restart applications using a plug-in when enabling or disabling.", @"Warn about restart panel message")];
	if (willEnable)
		[alert setInformativeText:L0PlugSuitLocalizedString(@"Plug-ins are enabled only when an application launches. If the plug-in doesn't seem to be working, try restarting its application.", @"Warn about restart panel info text (enabling)")];
	else
		[alert setInformativeText:L0PlugSuitLocalizedString(@"The plug-in will not be loaded the next time its application is started.", @"Warn about restart panel info text (disabling)")];
		
	[alert beginSheetModalForWindow:[[self mainView] window] modalDelegate:nil didEndSelector:NULL contextInfo:nil];
	[alert autorelease];
}

@end
